﻿using System;
using System.Collections.Generic;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using LK = LinqKit;
using Dimok.Shared.Filtering;

namespace Dimok.Web.Filtering {

  ////////////////////////////////////////////////////////////////////////////////////////////////////
  /// <summary>	Row filter. </summary>
  ///
  /// <remarks>	Dimon, 12/21/2010. </remarks>
  ////////////////////////////////////////////////////////////////////////////////////////////////////

  public static class RowFilter {

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>	Parses. </summary>
    ///
    /// <remarks>	Dimon, 12/21/2010. </remarks>
    ///
    /// <typeparam name="TEntity">	Type of the entity. </typeparam>
    /// <param name="filters">	The filters. </param>
    ///
    /// <returns>	. </returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    public static Expression<Func<TEntity, bool>> Parse<TEntity>(Dictionary<string, string> filters) where TEntity : class {
      var predicate = LK.PredicateBuilder.True<TEntity>();
      foreach (var f in filters) {
        var memberType = typeof(TEntity).GetProperty(f.Key).PropertyType;
        var fq = GetFilterQuery(f.Value, memberType);
        var le = System.Linq.Dynamic.DynamicExpression.ParseLambda<TEntity, bool>(string.Format(FilterTemplates[fq.Operator], f.Key, LinqParam(memberType, 0), fq.Values.Length > 1 ? LinqParam(memberType, 1) : ""), fq.Values[0], fq.Values[1]);
        predicate = LK.PredicateBuilder.And<TEntity>(predicate, le);
      }
      return predicate;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>	Parses. </summary>
    ///
    /// <remarks>	Dimon, 12/21/2010. </remarks>
    ///
    /// <param name="entityType">	Type of the entity. </param>
    /// <param name="filters">		The filters. </param>
    ///
    /// <returns>	. </returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    public static object[] Parse(Type entityType, Dictionary<string, string> filters) {
      var parameters = new List<object>();
      if (filters.Count == 0) return parameters.ToArray();
      var i = 0;
      var where = new List<string>();
      foreach (var f in filters) {
        var memberType = entityType.GetProperty(f.Key).PropertyType;
        var subTypeName = Regex.Match(memberType.AssemblyQualifiedName, @"\[\[(.+)\]\]").Groups[1].Value;
        if (subTypeName != "") memberType = Type.GetType(subTypeName);
        var fq = GetFilterQuery(f.Value, memberType);
        var exp = "";
        if (fq.Operator != FilterOperator.IsContainedIn)
          exp = string.Format(
            FilterTemplates[fq.Operator],
            f.Key, LinqParam(memberType, i++),
            fq.Values.Length > 1 ? LinqParam(memberType, i) : ""
            );
        else {
          var ors = new List<string>();
          foreach (var v in fq.Values) {
            ors.Add(
              string.Format(
                FilterTemplates[FilterOperator.IsEqualTo],
                f.Key,
                LinqParam(memberType, i++), null));
            parameters.Add(Convert.ChangeType(v, memberType));
          }
          exp = "(" + string.Join(" || ", ors.ToArray()) + ")";
        }
        if (fq.Values.Length == 1 || fq.Operator != FilterOperator.IsContainedIn) {
          if (fq.Values.Length > 1) i++;
          parameters.Add(Convert.ChangeType(fq.Values[0], memberType));
          if (fq.Values.Length > 1) parameters.Add(Convert.ChangeType(fq.Values[1], memberType));
        }
        where.Add(exp);
      }
      parameters.Insert(0, string.Join(" && ", where.ToArray()));
      return parameters.ToArray();
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>	Linq parameter. </summary>
    ///
    /// <remarks>	Dimon, 12/21/2010. </remarks>
    ///
    /// <param name="memberType">	Type of the member. </param>
    /// <param name="index">			Zero-based index of the. </param>
    ///
    /// <returns>	. </returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    static string LinqParam(Type memberType, int index) {
      //if (memberType == typeof(DateTime)) return "DateTime.Parse(@" + index + ")";
      //if (memberType == typeof(TimeSpan)) return "TimeSpan.Parse(@" + index + ")";
      return "@" + index;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>	Gets a filter queries. </summary>
    ///
    /// <remarks>	Dimon, 12/21/2010. </remarks>
    ///
    /// <param name="entityType">	Type of the entity. </param>
    /// <param name="filters">		The filters. </param>
    ///
    /// <returns>	The filter queries. </returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    public static FilterQuery[] GetFilterQueries(Type entityType, Dictionary<string, string> filters) {
      var fqs = new List<FilterQuery>();
      foreach (var f in filters) {
        var memberType = entityType.GetProperty(f.Key).PropertyType;
        var subTypeName = Regex.Match(memberType.AssemblyQualifiedName, @"\[\[(.+)\]\]").Groups[1].Value;
        if (subTypeName != "") memberType = Type.GetType(subTypeName);
        fqs.Add(GetFilterQuery(f.Value, memberType));
      }
      return fqs.ToArray();
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>	Gets a filter query. </summary>
    ///
    /// <remarks>	Dimon, 12/21/2010. </remarks>
    ///
    /// <param name="query">			The query. </param>
    /// <param name="queryType">	Type of the query. </param>
    ///
    /// <returns>	The filter query. </returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    public static FilterQuery GetFilterQuery(string query, Type queryType) {
      FilterOperator filter = FilterOperator.NoFilter;
      var values = new List<string>();
      if (query.EndsWith("*") && query.StartsWith("*")) {
        filter = FilterOperator.Contains;
        values.Add(query.Substring(1, query.Length - 2));
      } else if (query.StartsWith("*")) {
        filter = FilterOperator.EndsWith;
        values.Add(query.Substring(1, query.Length - 1));
      } else if (query.EndsWith("*")) {
        filter = FilterOperator.StartsWith;
        values.Add(query.Substring(0, query.Length - 1));
      } else if (query.StartsWith("=")) {
        filter = FilterOperator.IsEqualTo;
        values.Add(query.Substring(1, query.Length - 1));
      } else if (query.StartsWith("<>")) {
        filter = FilterOperator.IsNotEqualTo;
        values.Add(query.Substring(2, query.Length - 2));
      } else if (query.StartsWith("<=")) {
        filter = FilterOperator.IsLessThanOrEqualTo;
        values.Add(query.Substring(2, query.Length - 2));
      } else if (query.StartsWith(">=")) {
        filter = FilterOperator.IsGreaterThanOrEqualTo;
        values.Add(query.Substring(2, query.Length - 2));
      } else if (query.StartsWith("><") || Regex.IsMatch(query, @"^\S+\s+\S+$")) {
        filter = FilterOperator.IsBetween;
        values.AddRange(Regex.Split(query.Replace("><", ""), @"\s+"));
        //value1 = Regex.Match(query, @"\S+", RegexOptions.RightToLeft).Value;
        //query = Regex.Match(query.Substring(2), @"\S+").Value;
      } else if (Regex.IsMatch(query, ".+,.+")) {
        filter = FilterOperator.IsContainedIn;
        values.AddRange(query.Split(','));
      } else if (query.StartsWith("<")) {
        filter = FilterOperator.IsLessThan;
        values.Add(query.Substring(1, query.Length - 1));
      } else if (query.StartsWith(">")) {
        filter = FilterOperator.IsGreaterThan;
        values.Add(query.Substring(1, query.Length - 1));
      } else if (query + "" != "") {
        filter = queryType == typeof(string) ? FilterOperator.Contains : FilterOperator.IsEqualTo;
        values.Add(query);
      };
      //FilterOperator[] operators = new[] { FilterOperator.Contains, FilterOperator.EndsWith, FilterOperator.IsContainedIn, FilterOperator.StartsWith };
      //if (queryType != typeof(string) && operators.Contains(filter)) filter = FilterOperator.IsEqualTo;
      return new FilterQuery(query, filter, values.ToArray(), queryType);
    }
    static Dictionary<FilterOperator, string> FilterTemplates = GetFilterTemplates();

    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>	Gets the filter templates. </summary>
    ///
    /// <remarks>	Dimon, 12/21/2010. </remarks>
    ///
    /// <returns>	The filter templates. </returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////

    static Dictionary<FilterOperator, string> GetFilterTemplates() {
      return new Dictionary<FilterOperator, string>() {
        {FilterOperator.Contains,"{0}.Contains({1}){2}"},
        {FilterOperator.DoesNotContain, "!{0}.Contains({1}){2}"},
        {FilterOperator.StartsWith, "{0}.StartsWith({1}){2}"},
        {FilterOperator.EndsWith, "{0}.EndsWith({1}){2}"},
        {FilterOperator.IsEqualTo, "{0} = {1}{2}"},
        {FilterOperator.IsNotEqualTo, "{0} <> {1}{2}"},
        {FilterOperator.IsGreaterThan, "{0} > {1}{2}"},
        {FilterOperator.IsLessThan, "{0} < {1}{2}"},
        {FilterOperator.IsGreaterThanOrEqualTo, "{0} >= {1}{2}"},
        {FilterOperator.IsLessThanOrEqualTo, "{0} <= {1}{2}"},
        {FilterOperator.IsBetween, "({0} >= {1}) AND ({0} <= {2})"},
        {FilterOperator.IsNotBetween, "({0} < {1}) OR ({0} > {2})"},
        {FilterOperator.IsEmpty, "{0} = \"\"{1}{2}"},
        {FilterOperator.IsNotEmpty, "{0} <> \"\"{1}{2}"},
        {FilterOperator.IsNull, "{0} == null{1}{2}"},
        {FilterOperator.IsNotNull, "({0} != null){1}{2}"}
      };
    }
  }

}
